
/**
 * 
 * 
 */
ExtMVC.Route = function(mappingString, options) {
	this.mappingString = mappingString;
	this.options = options || {};

	// The regular expression we use to match a segment of a route mapping
	// this will recognise segments starting with a colon,
	// e.g. on 'namespace/:controller/:action', :controller and :action will be recognised
	this.paramMatchingRegex = new RegExp(/:([0-9A-Za-z\_]*)/g);

	/**
	 * Converts a route string into an array of symbols starting with a colon. e.g.
	 * ":controller/:action/:id" => [':controller', ':action', ':id']
	 */
	this.paramsInMatchString = this.mappingString
			.match(this.paramMatchingRegex)
			|| [];
	this.paramsInStringWithOptions = [];

	/**
	 * Store and remove any route conditions specified
	 */
	this.conditions = this.options.conditions || {};
	if (this.options.conditions) {
		delete this.options.conditions;
	}

	for (var i = 0; i < this.paramsInMatchString.length; i++) {
		this.paramsInStringWithOptions.push(this.paramsInMatchString[i]);
	};

	for (var o in options) {
		this.paramsInStringWithOptions.push(":" + o);
	}

	this.matcherRegex = this.convertToUsableRegex(mappingString);
};

ExtMVC.Route.prototype = {
	/**
	 * @param {url} The url we want to match against this route to see if it matches
	 * @return {boolean} Returns true if this route matches the url
	 */
	recognises				: function(url) {
		return this.matcherRegex.test(url);
	},

	/**
	 * @param {url} The url we want to provide matches for
	 * @return {Object} Object of all matches for this url, as well as additional params as defined in the route
	 */
	matchesFor				: function(url) {
		if (!this.recognises(url)) {
			return false;
		};

		var parameters = {};

		var keys = this.paramsInMatchString;
		var values = url.match(this.matcherRegex);
		values.shift(); // first value is the entire match so reject

		for (var i = keys.length - 1; i >= 0; i--) {
			parameters[keys[i].replace(":", "")] = values[i];
		};

		// add any additional parameter options specified in the route definition
		for (option in this.options) {
			parameters[option] = this.options[option];
		}

		return parameters;
	},

	urlForNamed				: function(options) {
		var options = options || {};

		return this.urlFor(Ext.applyIf(options, this.options));
	},

	/**
	 * Attempts to build a url with this route, swapping the placeholders with properties of the options hash
	 */
	urlFor					: function(options) {
		var url = this.mappingString;

		for (var o in options) {
			// values in options must match this.options - e.g. this.options.action must be the same as options.action
			if (options[o] && this.options[o] && options[o] != this.options[o]) {
				return false;
			}
		}

		// TODO: Tidy this up. All of it

		var paramsInOptions = [];
		for (var o in options) {
			paramsInOptions.push(":" + o);
		}

		paramsInOptions = paramsInOptions.sort();
		var paramsInStringWithOptions = this.paramsInStringWithOptions.sort();

		// make sure that all match elements in the url string are included. If not, return false
		if (paramsInStringWithOptions.length != paramsInOptions.length) {
			return false;
		}
		for (var i = 0; i < paramsInOptions.length; i++) {
			if (paramsInOptions[i] != paramsInStringWithOptions[i]) {
				return false;
			}
		};

		for (var o in options) {
			url = url.replace(":" + o, options[o]);
		}

		return url;
	},

	/**
	 * Private: For a given string, replaces all substrings starting with a colon into
	 * a regex string that can match a url segment. 
	 * e.g. :controller/:action => '^([a-zA-Z0-9\_]+)/([a-zA-Z0-9\_]+)$'
	 * If any conditions have been specified on this route, their regular expressions are used instead:
	 * :controller/:action/:id => '^([a-zA-Z0-9\_]+)/([a-zA-Z0-9\_]+)/([0-9]+)$'
	 * if conditions was set to {":id" =>/[0-9]+]/}
	 * @param {String} regex_string The string we want to turn into a matchable regex
	 * @return {String} The replaced string
	 */
	convertToUsableRegex	: function(regex_string) {
		var p = this.paramsInMatchString;

		for (var i = p.length - 1; i >= 0; i--) {
			var cond = this.conditions[p[i]];
			var matcher = String.format("({0})", cond || "[a-zA-Z0-9\_,]+");

			regex_string = regex_string.replace(new RegExp(p[i]), matcher);
		};

		// we want to match the whole string, so include the anchors
		return new RegExp("^" + regex_string + "$");
	}
};